﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Reflection;
using System.Collections;
using System.IO;
using System.Diagnostics.Contracts;

using King.Extensions;
using King.Text;
using King.Reflection;
using King.Language;
using King.Language.CSharp.Antlr3;

using Antlr.Runtime;
using Antlr.Runtime.Tree;

namespace King.Language.CSharp {

    public enum CSharpAstNodeType {
        ROOT,

        BOOLEAN_LITERAL,
        CLOSE_PARENTHESIS,
        COMMA,
        CONDITIONAL_SYMBOL,
        DELIMITED_COMMENT,
        DOUBLE_AMPERSTAND,
        DOUBLE_BAR,
        DOUBLE_EQUAL,
        EXCLAMATION,
        EXCLAMATION_EQUAL,
        INTEGER,
        NEW_LINE,
        OPEN_PARENTHESIS,

        PP_DEFINE,
        PP_ELSE,
        PP_ELSE_IF,
        PP_END_IF,
        PP_END_REGION,
        PP_ERROR,
        PP_LINE,
        PP_LINE_DEFAULT,
        PP_LINE_HIDDEN,
        PP_IF,
        PP_REGION,
        PP_SKIPPED_CHARACTERS,
        PP_UNDEFINE,
        PP_WARNING,

        PRAGMA_WARNING_DISABLE,
        PRAGMA_WARNING_RESTORE,

        SINGLE_LINE_COMMENT,
        WHITESPACE,
    }
    public enum CSharpExpressionType {
        Not,
        OrElse,
        AndAlso,
        Equal,
        NotEqual,
        Constant,
        Variable,
    }

    public abstract class CSharpAst : Ast {

        public static IEnumerable<CSharpAst> Parse(string code,
            IEnumerable<string> preProcessorSymbols = null, bool includePreProcessor = false, bool includeComments = false) {

            return Parse(code, "start", preProcessorSymbols, includePreProcessor, includeComments).ToEnumerable<CSharpAst>();
        }

        internal static object Parse(string code, string rule,
            IEnumerable<string> preProcessorSymbols = null, bool includePreProcessor = false, bool includeComments = false) {
            
            // activate parser
            var stream = new ANTLRStringStream(code);
            var lexer = (ITokenSource)new Antlr3.CSharpLexer(stream);
            var tokens = new CommonTokenStream(lexer);
            var parser = new Antlr3.CSharpParser(tokens);

            // set options
            parser.EmitPreProcessor = includePreProcessor;
            parser.EmitComments = includeComments;
            parser.AddPreProcessorSymbols(preProcessorSymbols);

            // parse code
            var scope = (IAstRuleReturnScope)typeof(Antlr3.CSharpParser).Invoke(rule, parser);
            var tree = (CommonTree)scope.Tree;

            if (tree == null)
                return Enumerable.Empty<CSharpAst>();

            // verify ast
            //var treeStream = new CommonTreeNodeStream(tree);
            //var walker = new Antlr3.CSharpAst(treeStream);
            //typeof(Antlr3.CSharpAst).Invoke(rule, walker);

            if (!tree.IsNil)
                return Construct(tree);

            return tree.Children.Select(o => Construct((CommonTree)o));
        }

        private static CSharpAst ConstructComments(CommonTree tree, Token token) {
            
            var type = tree.Token.Type;

            // comment delimited
            if (type == CSharpLexer.DELIMITED_COMMENT)
                return new CSharpComment(Regex.Match(token.Text, @"^[/][*](?<comment>.*)[*][/]$").Get("comment"), true, token);

            // comment single-line
            else if (type == CSharpLexer.SINGLE_LINE_COMMENT)
                return new CSharpComment(Regex.Match(token.Text, @"^[/][/](?<comment>.*)$").Get("comment"), false, token);

            return null;
        }
        private static CSharpAst ConstructPreProcessor(CommonTree tree, IEnumerable<CSharpAst> children, Token token) {

            var type = tree.Token.Type;
            var parentType = tree.Parent == null ? 0 : tree.Parent.Type;

            // #define
            if (type == CSharpLexer.PP_DEFINE)
                return new CSharpPreProcessorDeclaration(true, children, token);

            // #undef
            else if (type == CSharpLexer.PP_UNDEFINE)
                return new CSharpPreProcessorDeclaration(false, children, token);

            else if (type == CSharpLexer.PP_WARNING || type == CSharpLexer.PP_ERROR) {
                var message = Regex.Match(token.Text, @"^#(warning|error) (?<message>.*)$").Get("message");

                // #warning
                if (type == CSharpLexer.PP_WARNING)
                    return new CSharpPreProcessorWarning(message, token);

                // #error
                else if (type == CSharpLexer.PP_ERROR)
                    return new CSharpPreProcessorError(message, token);
            }

            // #pragma warning restore
            else if (type == CSharpLexer.PP_PRAGMA_WARNING_RESTORE)
                return new CSharpPreProcessorPragma(true, children, token);

            // #pragma warning disable
            else if (type == CSharpLexer.PP_PRAGMA_WARNING_DISABLE)
                return new CSharpPreProcessorPragma(false, children, token);

            // #region
            else if (type == CSharpLexer.PP_REGION)
                return new CSharpPreProcessorRegion(Regex.Match(token.Text, @"^#region\s*(?<value>.*)$").Get("value"), children, token);

            // #line default
            else if (type == CSharpLexer.PP_LINE_DEFAULT)
                return new CSharpPreProcessorLine(false, token);

            // #line hidden
            else if (type == CSharpLexer.PP_LINE_HIDDEN)
                return new CSharpPreProcessorLine(true, token);

            // #line
            else if (type == CSharpLexer.PP_LINE) {
                var line = Regex.Match(token.Text, @"^#line\s*(?<line>\d+)\s*([""](?<file>.*)[""])?$");
                return new CSharpPreProcessorLine(line.Get<int>("line"), line.Get("file"), token);

            // <conditional>
            } else if (type == CSharpLexer.PP_CONDITIONAL)
                return new CSharpPreProcessorConditional(children);

            // #if
            else if (type == CSharpLexer.PP_IF)
                return new CSharpPreProcessorIf(children, token);

            // #elif
            else if (type == CSharpLexer.PP_ELSE_IF)
                return new CSharpPreProcessorElif(children, token);

            // #else
            else if (type == CSharpLexer.PP_ELSE)
                return new CSharpPreProcessorElse(children, token);

            // if-defed-out
            else if (type == CSharpLexer.PP_SKIPPED_CHARACTERS)
                return new CSharpPreProcessorSkip(token.Text, token);

            // pre-processor symbol
            else if (type == CSharpLexer.CONDITIONAL_SYMBOL) {

                // declaration
                if (parentType == CSharpLexer.PP_DEFINE || parentType == CSharpLexer.PP_UNDEFINE)
                    return new CSharpSymbol(token.Text, token);
            }

            return null;
        }
        private static CSharpAst ConstructExpression(CommonTree tree, IEnumerable<CSharpAst> children, Token token) {

            var type = tree.Token.Type;

            // binary operator "&&"
            if (type == CSharpLexer.DOUBLE_AMPERSTAND)
                return new CSharpBinaryExpression(CSharpExpressionType.AndAlso, children, token);

            // binary operator "||"
            else if (type == CSharpLexer.DOUBLE_BAR)
                return new CSharpBinaryExpression(CSharpExpressionType.OrElse, children, token);

            // binary operator "=="
            else if (type == CSharpLexer.DOUBLE_EQUAL)
                return new CSharpBinaryExpression(CSharpExpressionType.Equal, children, token);

            // binary operator "!="
            else if (type == CSharpLexer.EXCLAMATION_EQUAL)
                return new CSharpBinaryExpression(CSharpExpressionType.NotEqual, children, token);

            // unary operator "!"
            else if (type == CSharpLexer.EXCLAMATION)
                return new CSharpUnaryExpression(CSharpExpressionType.Not, children, token);

            // true/false
            else if (type == CSharpLexer.BOOLEAN_LITERAL)
                return new CSharpConstantExpression(bool.Parse(token.Text), token);

            // true/false
            else if (type == CSharpLexer.INTEGER)
                return new CSharpConstantExpression(int.Parse(token.Text), token);

            return null;
        }
        private static CSharpAst Construct(CommonTree tree) {

            var commonToken = (CommonToken)tree.Token;
            var type = tree.Token.Type;
            var token = new Token(commonToken.Text,
                commonToken.Line, commonToken.CharPositionInLine,
                commonToken.StartIndex, commonToken.StopIndex - commonToken.StartIndex,
                commonToken.TokenIndex);

            var children = tree.GetChildren().Select(o => Construct(o));

            CSharpAst node = null;

            // pre-processor
            if ((node = ConstructPreProcessor(tree, children, token)) != null)
                return node;

            // comment
            else if ((node = ConstructComments(tree, token)) != null)
                return node;

            // expression
            else if ((node = ConstructExpression(tree, children, token)) != null)
                return node;

            else if (type == CSharpLexer.CONDITIONAL_SYMBOL)
                return new CSharpVariableExpression(token.Text, token);

            else
                throw new NotImplementedException();
        }

        protected CSharpAst(IEnumerable<Ast> children = null, Token token = null)
            : base(children, token) {
        }
    }

    public abstract class CSharpPreProcessorCode : CSharpAst {

        protected CSharpPreProcessorCode(IEnumerable<Ast> children = null, Token token = null)
            : base(children, token) {
        }
    }
    public sealed class CSharpPreProcessorDeclaration : CSharpPreProcessorCode {

        private bool m_define;

        public CSharpPreProcessorDeclaration(bool define, IEnumerable<Ast> children, Token token = null) 
            : base(children, token) {
            m_define = define;
        }

        public bool IsDefine {
            get { return m_define; }
        }
        public string Symbol {
            get { return ((CSharpSymbol)Children().Single()).Symbol; }
        }
    }

    public sealed class CSharpPreProcessorConditional : CSharpPreProcessorCode {

        public CSharpPreProcessorConditional(IEnumerable<CSharpAst> children = null)
            : base(children) {
        }

        public CSharpPreProcessorIf If {
            get { return (CSharpPreProcessorIf)Children().First(); }
        }
        public IEnumerable<CSharpPreProcessorElif> Elifs() {
            return Children().Skip(1).TakeWhile(o => o is CSharpPreProcessorElif).Cast<CSharpPreProcessorElif>();
        }
        public CSharpPreProcessorElse Else {
            get { return Children().Last() as CSharpPreProcessorElse; }
        }
    }
    public sealed class CSharpPreProcessorIf : CSharpPreProcessorCode {

        public CSharpPreProcessorIf(IEnumerable<CSharpAst> children = null, Token token = null)
            : base(children, token) {
        }

        public CSharpExpression Test {
            get { return (CSharpExpression)Children().First(); }
        }
    }
    public sealed class CSharpPreProcessorElif : CSharpPreProcessorCode {

        public CSharpPreProcessorElif(IEnumerable<CSharpAst> children = null, Token token = null)
            : base(children, token) {
        }

        public CSharpExpression Test {
            get { return (CSharpExpression)Children().First(); }
        }
    }
    public sealed class CSharpPreProcessorElse : CSharpPreProcessorCode {

        public CSharpPreProcessorElse(IEnumerable<CSharpAst> children = null, Token token = null)
            : base(children, token) {
        }
    }

    public sealed class CSharpPreProcessorSkip : CSharpPreProcessorCode {

        private string m_text;

        public CSharpPreProcessorSkip(string text, Token token = null)
            : base(token: token) {

            m_text = text;
        }

        public string Text {
            get { return m_text; }
        }
    }
    public sealed class CSharpPreProcessorPragma : CSharpPreProcessorCode {

        private bool m_restore;

        public CSharpPreProcessorPragma(bool restore, IEnumerable<CSharpAst> children = null, Token token = null)
            : base(children, token) {

            m_restore = restore;
        }

        public IEnumerable<int> Warnings() {
            return Children().Cast<CSharpConstantExpression>().Select(o => (int)o.Value);
        }
        public bool Restore {
            get { return m_restore; }
        }
    }
    public sealed class CSharpPreProcessorWarning : CSharpPreProcessorCode {

        private string m_message;

        public CSharpPreProcessorWarning(string message, Token token = null)
            : base(token: token) {

            m_message = message;
        }

        public string Message {
            get { return m_message; }
        }
    }
    public sealed class CSharpPreProcessorError : CSharpPreProcessorCode {

        private string m_message;

        public CSharpPreProcessorError(string message, Token token = null)
            : base(token: token) {

            m_message = message;
        }

        public string Message {
            get { return m_message; }
        }
    }
    public sealed class CSharpPreProcessorRegion : CSharpPreProcessorCode {

        private string m_value;

        public CSharpPreProcessorRegion(string value, IEnumerable<CSharpAst> children = null, Token token = null)
            : base(children, token) {

            m_value = value;
        }

        public string Value {
            get { return m_value; }
        }
    }
    public sealed class CSharpPreProcessorLine : CSharpPreProcessorCode {

        private int m_line;
        private string m_fileName;
        private bool? m_hidden;

        public CSharpPreProcessorLine(bool hidden, Token token = null)
            : base(token: token) {
            m_hidden = hidden;
            m_line = -1;
        }
        public CSharpPreProcessorLine(int line, string fileName, Token token = null)
            : base(token: token) {

            m_line = line;
            m_fileName = fileName;
        }

        public bool Hidden {
            get { return m_hidden == true; }
        }
        public bool Default {
            get { return m_hidden == false; }
        }
        public string FileName {
            get { return m_fileName; }
        }
        public int NewLine {
            get { return m_line; }
        }
    }

    public sealed class CSharpSymbol : CSharpPreProcessorCode {

        private string m_symbol;

        public CSharpSymbol(string symbol, Token token = null)
            : base(token: token) {
            m_symbol = symbol;
        }

        public string Symbol {
            get { return m_symbol; }
        }
    }
    public sealed class CSharpComment : CSharpPreProcessorCode {

        private string m_comment;
        private bool? m_delimited;

        public CSharpComment(string comment, bool? delimited = null, Token token = null)
            : base(token: token) {
            m_comment = comment;
            m_delimited = delimited;
        }

        public string Value {
            get { return m_comment; }
        }
        public bool? Delimited {
            get { return m_delimited; }
        }
    }

    public abstract class CSharpExpression : CSharpPreProcessorCode {

        public static CSharpExpression Parse(string code) {
            return (CSharpExpression)Parse(code, "pp_expression");
        }

        private CSharpExpressionType m_expressionType;

        protected CSharpExpression(CSharpExpressionType expressionType, IEnumerable<CSharpAst> children = null, Token token = null)
            : base(children, token) {

            m_expressionType = expressionType;
        }

        public CSharpExpressionType ExpressionType {
            get { return m_expressionType; }
        }
    }
    public sealed class CSharpVariableExpression : CSharpExpression {

        private string m_variable;

        public CSharpVariableExpression(string variable, Token token = null)
            : base(CSharpExpressionType.Variable, token: token) {

                m_variable = variable;
        }

        public string Symbol {
            get { return m_variable; }
        }
    }
    public sealed class CSharpConstantExpression : CSharpExpression {

        private object m_value;

        public CSharpConstantExpression(object value, Token token = null)
            : base(CSharpExpressionType.Constant, token: token) {

            m_value = value;
        }

        public object Value {
            get { return m_value; }
        }
    }
    public sealed class CSharpBinaryExpression : CSharpExpression {

        public CSharpBinaryExpression(
            CSharpExpressionType expressionType, IEnumerable<CSharpAst> children, Token token = null)
            : base(expressionType, children, token) {
        }

        public CSharpExpression Left {
            get { return (CSharpExpression)Children().First(); }
        }
        public CSharpExpression Right {
            get { return (CSharpExpression)Children().Second(); }
        }

    }
    public sealed class CSharpUnaryExpression : CSharpExpression {

        public CSharpUnaryExpression(
            CSharpExpressionType expressionType, IEnumerable<CSharpAst> children, Token token = null)
            : base(expressionType, children, token) {
        }

        public CSharpExpression Operand {
            get { return (CSharpExpression)Children().Single(); }
        }
    }
}
